{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению. Сессия № 2\n",
    "\n",
    "### <center> Автор материала: Петр Катренко (@petrucho)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "_cell_guid": "657faca0-c88f-4138-8c62-cf9974e0c894",
    "_uuid": "b30a120404b8e104774a292b45f0902e89acef68"
   },
   "source": [
    "## <center> Индивидуальный проект по анализу данных\n",
    "### <center> Изменение меток классов в задаче многоклассовой классификации с использованием данных википедии и алгоритма word2vec\n",
    "\n",
    "Этот проект ставит перед собой очень амбициозную, и оттого немного глупую задачу улучшения разметки данных. \n",
    "\n",
    "Для этого предполагается использовать алгоритм word2vec. В его описании в популярных источниках зачастую можно встретить утверждения, что он осуществляет преобразование пространства слов в пространство смыслов. Это именно то, что необходимо для разумной классификации. Более того, такое преобразование позволяет вместо логистической регрессии в пространство классов применить регрессию на компоненты вектора слова, чтобы затем выбрать класс, имеющий наиболее близкий к предсказанному вектор. Кроме того, такой метод позволил бы сравнительно легко дообучать модель предсказывать новые классы, просто добавляя их в word2vec.\n",
    "\n",
    "В качестве первичного набора данных, для которого мы хотим улучшить классификацию, используется часть набора разменных изображений imagenet с различными попродами собак, изображенных на нем. Эти данные представлены на соревновании https://www.kaggle.com/c/dog-breed-identification . Файл train.zip включает в себя архив изображеий собак. В файле labels.csv.zip записана порода собак, изображеных на картинках. Всего представлено 120 различных пород.\n",
    "\n",
    "\n",
    "Word2vec тренируется на следующих наборах данных:\n",
    "\n",
    "    1) Тексты англоязычных статей википедии о почти каждой из 120 представленных пород\n",
    "    2) Тексты англоязычных статей википедии по различным тематикам, так или иначе связанным с породами собак\n",
    "\n",
    "Все используемые данные, включая веса для word2vec и веса изображений, полученные из нейросети, можно скачать по ссылке: https://yadi.sk/d/jKtrYLoa3Pc49X. Подгружаются они из той же папки, в которой работает этот ipynb.\n",
    "Сами изображения ищите на кэггле.\n",
    "\n",
    "В качестве метрики используется точность (accuracy). Этот выбор кажется оптимальным в задаче многоклассовой классификации и, кроме того, эта метрика в некотором смысле унаследованна с конкурса на kaggle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "d4c4a3a8-93af-4cd2-a95b-32a2526ac3a2",
    "_uuid": "151b0f031d10c081017bee0831d1e276148b413b"
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import datetime as dt\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.axes_grid1 import ImageGrid\n",
    "from os import listdir, makedirs, getcwd, remove\n",
    "from os.path import isfile, join, abspath, exists, isdir, expanduser\n",
    "from tqdm import tqdm\n",
    "from keras.models import Model, Sequential\n",
    "from keras.layers import Input, Flatten, Dense, Conv2D, MaxPooling2D, Dropout\n",
    "from keras.utils import layer_utils\n",
    "from keras import backend as K\n",
    "from keras.optimizers import RMSprop, SGD, Adam\n",
    "from keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint, TensorBoard, CSVLogger\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.metrics import log_loss, accuracy_score\n",
    "from keras.preprocessing import image\n",
    "from keras.applications.vgg16 import VGG16\n",
    "from keras.applications.resnet50 import ResNet50\n",
    "from keras.applications import xception\n",
    "from keras.applications import inception_v3\n",
    "from keras.applications.vgg16 import preprocess_input, decode_predictions\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.decomposition import PCA\n",
    "import wikipedia\n",
    "import stemming\n",
    "import re\n",
    "from stemming.porter2 import stem\n",
    "from gensim.models import Word2Vec\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "import pickle\n",
    "from sklearn.linear_model import LinearRegression, Ridge\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from sklearn.model_selection import validation_curve\n",
    "import seaborn as sns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "c60a6481-e35a-49fd-b1b3-4c74f4adc472",
    "_uuid": "928b27a4686daca6a69bcf74bb592c9e99393992"
   },
   "outputs": [],
   "source": [
    "\n",
    "start = dt.datetime.now()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "8bcb2bda-88dc-4ad8-9177-0c126401c3e1",
    "_uuid": "3f62e82d998e8b2ba3999542492e632c5083a901"
   },
   "outputs": [],
   "source": [
    "INPUT_SIZE = 224 #Размер, к котормоу преобразуются все изображения\n",
    "SEED = 1987\n",
    "data_dir = ''\n",
    "labels = pd.read_csv(join(data_dir, 'labels.csv'))\n",
    "print(len(listdir(join(data_dir, 'train'))), len(labels))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print np.unique(labels.breed.values)[:10]\n",
    "print 'N of breeds =', len(np.unique(labels.breed.values))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "f55b18df-3698-4146-9157-913227416e21",
    "_uuid": "ab322ce7a697d43a883b1725c7f71bf4486fd5ed"
   },
   "outputs": [],
   "source": [
    "labels['target'] = 1\n",
    "labels['rank'] = labels.groupby('breed').rank()['id']\n",
    "labels_pivot = labels.pivot('id', 'breed', 'target').reset_index().fillna(0)\n",
    "np.random.seed(seed=SEED)\n",
    "rnd = np.random.random(len(labels))\n",
    "ftrain_idx = rnd < 0.8\n",
    "holdout_idx = rnd >= 0.8\n",
    "y_train = labels_pivot.values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "c48fc864-d70f-4045-96eb-12de12c0ad41",
    "_uuid": "7d26cc67909b5bd70173b5f2ed8352b210e06fb3"
   },
   "outputs": [],
   "source": [
    "def read_img(img_id, train_or_test, size):\n",
    "    \"\"\"Read and resize image.\n",
    "    # Arguments\n",
    "        img_id: string\n",
    "        train_or_test: string 'train' or 'test'.\n",
    "        size: resize the original image.\n",
    "    # Returns\n",
    "        Image as numpy array.\n",
    "    \"\"\"\n",
    "    img = image.load_img(join(data_dir, train_or_test, '%s.jpg' % img_id), target_size=size)\n",
    "    img = image.img_to_array(img)\n",
    "    return img"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = plt.figure(1, figsize=(16, 16))\n",
    "grid = ImageGrid(fig, 111, nrows_ncols=(4, 4), axes_pad=0.05)\n",
    "i=0\n",
    "for img_id, breed in zip(labels.id.values, labels.breed.values):\n",
    "    if breed!='staffordshire_bullterrier': continue\n",
    "    #if breed!='eskimo_dog': continue\n",
    "    ax = grid[i]\n",
    "    img = read_img(img_id, 'train', (224, 224))\n",
    "    ax.imshow(img / 255.)\n",
    "    i+=1\n",
    "    if i==16: break\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "_cell_guid": "5759352a-e324-468c-b0b9-b360962a823f",
    "_uuid": "dd270f61ff0278c9171592f4999513b460f8f262"
   },
   "source": [
    "# Извлекаем веса из одного из предпоследних слоев VGG16\n",
    "Лучше не запускать этот кусок кода, если у вас нет лишних 8 часов времени"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "INPUT_SIZE = 224\n",
    "POOLING = 'avg'\n",
    "MAX_LEN = len(labels['id']) \n",
    "vgg_bottleneck = VGG16(weights='imagenet', include_top=False, pooling=POOLING)\n",
    "for n_file in range(11):\n",
    "    x_train = np.zeros((min(n_file*1000+1000, MAX_LEN)-n_file*1000, INPUT_SIZE, INPUT_SIZE, 3), dtype='float32')\n",
    "    for i, img_id in tqdm(enumerate(labels['id'][n_file*1000:min(n_file*1000+1000, MAX_LEN)])):\n",
    "        img = read_img(img_id, 'train', (INPUT_SIZE, INPUT_SIZE))\n",
    "        x_train[i] = preprocess_input(np.expand_dims(img, axis=0))\n",
    "\n",
    "    train_vgg_bf = vgg_bottleneck.predict(x_train, batch_size=32, verbose=1)\n",
    "    np.savetxt('vgg16_bweights_train_full'+str(n_file)+'.txt', train_vgg_bf)\n",
    "    del train_vgg_bf\n",
    "    del x_train\n",
    "    del img"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "vgg16_bweights_train_full[0-10] is data set of the full available photos, e.g. 100+ breeds, 10222 dogs. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На данный момент мы имеем 512D мерное пространство признаков. Но, так-как веса данного классификатора изначально были тренированы на всем многообразии классов из imagenet, значительная часть этих признаков будет для нас совершенно бесполезна. Сделаем PCA преобразование, чтобы уменьшить их количество.\n",
    "\n",
    "Визуальный анализ распределения этих признаков самих по себе не имеет никакого смысла, так-как это признаки, сформированные довольно глубокой нейронной сетью на пикселях изображения."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# загрузим зписанные в файл веса\n",
    "vgg_bf = []\n",
    "for i in range(11):\n",
    "    vgg_bf.extend(np.loadtxt('vgg16_bweights_train_full'+str(i)+'.txt'))\n",
    "vgg_bf=np.array(vgg_bf)\n",
    "vgg_bf_holdout = vgg_bf[holdout_idx]\n",
    "vgg_bf = vgg_bf[ftrain_idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(seed=SEED)\n",
    "rnd = np.random.random(len(vgg_bf))\n",
    "train_idx=[i<0.8 for i in rnd]\n",
    "valid_idx=[i>=0.8 for i in rnd]\n",
    "train_vgg_bf = vgg_bf[train_idx]\n",
    "valid_vgg_bf = vgg_bf[valid_idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vgg_scaler = StandardScaler()\n",
    "train_vgg_bfs = vgg_scaler.fit_transform(train_vgg_bf)\n",
    "valid_vgg_bfs = vgg_scaler.transform(valid_vgg_bf)\n",
    "hold_vgg_bfs = vgg_scaler.transform(vgg_bf_holdout)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pca = PCA(random_state=137, n_components=train_vgg_bf.shape[1])\n",
    "X_t = pca.fit_transform(train_vgg_bfs)\n",
    "\n",
    "disp=[0]\n",
    "for nd in range(X_t.shape[1]):\n",
    "    ar = [i[nd] for i in X_t]\n",
    "    me = np.mean(ar)\n",
    "    disp.append(disp[-1]+sum([(i-me)**2 for i in ar])/X_t.shape[0])\n",
    "\n",
    "n=0\n",
    "for i in disp:\n",
    "    if(disp[-1]*0.9<i): break\n",
    "    n+=1\n",
    "print 'Number of the principal components for 95% dispersion finesse is', n\n",
    "\n",
    "plt.plot(range(len(disp)), disp)\n",
    "plt.plot([0,len(disp)], [0.9*disp[-1],0.9*disp[-1]], c='black')\n",
    "plt.plot([n,n], [0,disp[-1]], c='red')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Кривая дисперсии растет довольно медленно, поэтому мы вынужденны сохранить более половины признаков."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_vgg_bfP = [i[:273] for i in pca.transform(train_vgg_bfs)]\n",
    "valid_vgg_bfP = [i[:273] for i in pca.transform(valid_vgg_bfs)]\n",
    "vgg_bfP = [i[:273] for i in pca.transform(vgg_scaler.transform(vgg_bf))]\n",
    "hold_vgg_bfP = [i[:273] for i in pca.transform(hold_vgg_bfs)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Давайте проверим качество простого классификатора на этих данных. Целью данного проекта будет побить именно эту цифру, используя дополнительную информацию."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "breed_dict={}\n",
    "for i in np.unique(labels.breed.values): breed_dict[i]=len(breed_dict)+1\n",
    "y = np.array([breed_dict[i] for i in labels.breed.values[ftrain_idx]])\n",
    "logreg = LogisticRegression(multi_class='multinomial', solver = 'lbfgs', random_state=137, C=1)\n",
    "logreg.fit(vgg_bfP, y)\n",
    "Cs = np.logspace(-5, -1, 20)\n",
    "val_train, val_test = validation_curve(logreg, vgg_bfP, y,'C', Cs, cv=2, scoring='accuracy', verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_with_err(x, data, **kwargs):\n",
    "    mu, std = data.mean(1), data.std(1)\n",
    "    lines = plt.plot(x, mu, '-', **kwargs)\n",
    "    plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "    facecolor=lines[0].get_color(), alpha=0.2)\n",
    "\n",
    "plot_with_err(Cs, val_train, label='training scores')\n",
    "plot_with_err(Cs, val_test, label='validation scores')\n",
    "plt.xlabel('C'); plt.ylabel('accuracy')\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Также проверим качество классификации с использованием всех компонент"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "val_train2, val_test2 = validation_curve(logreg, vgg_bf, y,'C', Cs, cv=2, scoring='accuracy', verbose=1)\n",
    "\n",
    "plot_with_err(Cs, val_train2, label='training scores')\n",
    "plot_with_err(Cs, val_test2, label='validation scores')\n",
    "plt.xlabel('C'); plt.ylabel('accuracy')\n",
    "plt.legend()\n",
    "\n",
    "print max(val_test.mean(1)), max(val_test2.mean(1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что даже 95% дисперсии оказалось недостаточным для полноценной классификации - РСА ухудшает точность на 7%. \n",
    "Точность на холдауте совпадает."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logreg = LogisticRegression(multi_class='multinomial', solver = 'lbfgs', random_state=137, C=0.01)\n",
    "logreg.fit(vgg_bf, y)\n",
    "print('Accuracy on holdout {}'.format(accuracy_score(\n",
    "    logreg.predict(vgg_bf_holdout), [breed_dict[i] for i in labels.breed.values[holdout_idx]])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# Получение весов word2vec"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Для начала займемся парсингом статей из википедии. К счстью для этого есть простая и удобная библиотека wikipedia. \n",
    "\n",
    "Сделаем ва прохода: \n",
    "    \n",
    "    В одном пройдем по всем статьям, выпадающим в поиске при запросе интересующей нас породы. В этом \n",
    "    случае, например, для породы saint bernard в нашу выборку попадает так же статья о святом Бернарде.\n",
    "    \n",
    "    В другом сделаем так, чтобы на каждую породу приходилась ровно 1 статья. К сожалению для этого пришлось \n",
    "    провести ручгую разметку половины пород.\n",
    "    \n",
    "Также в обоих случаях все входящие слова приводятся к нижнему регистру и лемматизируются. Все символы(включая числа) также убраны."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "breeds = np.unique(labels.breed)\n",
    "\n",
    "list_of_weird_breeds = {\n",
    "    'african_hunting_dog' : 'African wild dog',\n",
    "    'appenzeller' : 'Appenzeller Sennenhund', \n",
    "    'basset' : 'Basset Hound',\n",
    "    'black-and-tan_coonhound' :'Black and Tan Coonhound',\n",
    "    'blenheim_spaniel' : 'Cavalier King Charles Spaniel',\n",
    "    'bluetick' : 'Bluetick Coonhound',\n",
    "    'boston_bull' : 'Boston Terrier', \n",
    "    'boxer' : 'Boxer (dog)', \n",
    "    'brabancon_griffon': 'Griffon Bruxellois', \n",
    "    'brittany_spaniel' : 'Brittany dog', \n",
    "    'bull_mastiff' : 'Bullmastiff', \n",
    "    'cardigan' : 'Cardigan Welsh Corgi',\n",
    "    'chihuahua' : 'Chihuahua (dog)', \n",
    "    'clumber': 'Clumber Spaniel',\n",
    "    'curly-coated_retriever' : 'Curly Coated Retriever', \n",
    "    'dandie_dinmont' : 'Dandie Dinmont Terrier', \n",
    "    'doberman' : 'Doberman Pinscher', \n",
    "    'english_springer' : 'English Springer Spaniel',\n",
    "    'entlebucher' : 'Entlebucher Mountain Dog',\n",
    "    'eskimo_dog' : 'Husky',\n",
    "    'german_short-haired_pointer' : 'German Shorthaired Pointer', \n",
    "    'groenendael' : 'Groenendael dog', \n",
    "    'japanese_spaniel' : 'Japanese Chin', \n",
    "    'malamute' : 'Alaskan Malamute', \n",
    "    'mexican_hairless' : 'Mexican Hairless Dog',\n",
    "    'miniature_poodle' : 'Poodle', \n",
    "    'newfoundland' : 'Newfoundland dog',\n",
    "    'papillon' : 'Papillon dog',\n",
    "    'pekinese' : 'Pekingese', \n",
    "    'pembroke' : 'Pembroke Welsh Corgi',\n",
    "    'pomeranian' : 'Pomeranian dog',\n",
    "    'redbone' : 'Redbone Coonhound',\n",
    "    'scotch_terrier' : 'Scottish Terrier', \n",
    "    'shih-tzu' : 'Shih Tzu',\n",
    "    'silky_terrier' : 'Australian Silky Terrier', \n",
    "    'standard_poodle' : 'Poodle',\n",
    "    'staffordshire_bullterrier': 'staffordshire bull terrier',\n",
    "    'saint_bernard' : 'St. Bernard (dog)',\n",
    "    'samoyed' : 'Samoyed dog', \n",
    "    'toy_poodle' : 'Poodle', \n",
    "    'walker_hound' : 'Treeing Walker Coonhound',\n",
    "    'wire-haired_fox_terrier' : 'Wire Fox Terrier', \n",
    "    }\n",
    "\n",
    "rejected_words = []\n",
    "\n",
    "def GetWikiText(breed): # Get text of one wiki page per breed\n",
    "    topic = ''\n",
    "    for i in wikipedia.search(breed):\n",
    "        if (breed.replace('_', ' ') == i.lower()):\n",
    "            topic = i\n",
    "            break\n",
    "    if breed in list_of_weird_breeds.keys():\n",
    "        topic = list_of_weird_breeds[breed]\n",
    "    if topic=='':\n",
    "        print 'Somethig is wrong with breed', breed, 'Try these: ', wikipedia.search(breed)\n",
    "        return []\n",
    "    try: pag = wikipedia.page(topic).content\n",
    "    except wikipedia.DisambiguationError: \n",
    "        print 'Topic ', topic, ' for the breed ',  breed, ' include multiple entries. Here is the list: ', wikipedia.search(topic)\n",
    "        return []\n",
    "    sentences = pag.lower().rsplit('.')         #lower text and split on sentences\n",
    "    sentences = [[stem(word) for word in re.sub('\\W+',' ', sentence).rsplit(' ')] \n",
    "                 for sentence in sentences]     #remove not alphabet/numbers, split on words\n",
    "    sentences = [[word for word in sentence if (word not in rejected_words)&(word!='')] \n",
    "                 for sentence in sentences]     #choose only entries of our vocab\n",
    "    return sentences\n",
    "\n",
    "def GetWikiRaw(breed): # Get text of all relevant to the breed pages\n",
    "    text = []\n",
    "    print '|', breed,\n",
    "    for topic in wikipedia.search(breed):\n",
    "        try: pag = wikipedia.page(topic).content\n",
    "        except wikipedia.DisambiguationError: \n",
    "            print topic, \n",
    "            continue\n",
    "        sentences = pag.lower().rsplit('.')         #lower text and split on sentences\n",
    "        sentences = [[stem(word) for word in re.sub('\\W+',' ', sentence).rsplit(' ')] \n",
    "                     for sentence in sentences]     #remove not alphabet/numbers, split on words\n",
    "        sentences = [[word for word in sentence if (word not in rejected_words)&(word!='')] \n",
    "                     for sentence in sentences]     #choose only entries of our vocab\n",
    "        text.extend(sentences)\n",
    "    return text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "text = []\n",
    "a=[text.extend(GetWikiText(i)) for i in breeds]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на наиболее часто встречающиеся в статьях слова"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "word_dict={}\n",
    "for sentence in text:\n",
    "    for word in sentence:\n",
    "        if word not in word_dict: word_dict[word]=1\n",
    "        else: word_dict[word]=word_dict[word]+1\n",
    "print [i[0] for i in sorted([(i, word_dict[i]) for i in word_dict], key=lambda i: i[1])[-30:]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "К сожалению для нашего собачьего датасета уже 30 самых популярных слов включают слова 'dog', 'breed', а среди 50 самых популярных начинают встречаться названия самих пород. С другой стороны мы можем вычеркнуть слова. встретившиея всего раз, а таких 5700. И вот список исключенных наиболее частых слов:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rejected_words = [u'can', u'were', u'this', u'not', u'at', u'their', u'on', u's', u'have', u'from', \n",
    "                  u'they', u'was', u'by', u'or', u'that', u'for', u'it', u'be', u'with', u'are', u'as', \n",
    "                  u'is', u'to', u'a', u'in', u'of', u'and', u'the']\n",
    "rejected_words.extend([i[0] for i in sorted([(i, word_dict[i]) for i in word_dict], key=lambda i: i[1]) if i[1]<=1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print len(rejected_words), len(word_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Повторим ту же процедуру для статей более широкой тематики"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "textR = []\n",
    "a=[textR.extend(GetWikiRaw(i)) for i in breeds]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "word_dict={}\n",
    "for sentence in textR:\n",
    "    for word in sentence:\n",
    "        if word not in word_dict: word_dict[word]=1\n",
    "        else: word_dict[word]=word_dict[word]+1\n",
    "print [i[0] for i in sorted([(i, word_dict[i]) for i in word_dict], key=lambda i: i[1])[-50:]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rejected_wordsR = [u'one', u'most', u'year', u'been', u'group', u'coat', u'show', u'can', u'work', u'kennel', u'use',\n",
    "                   u'has', u'but', u'he', u'other', u'his', u'which', u'this', u'also', u'an', u'club', u'not', \n",
    "                   u'their', u'were', u'have', u'they', u'at', u'or', u'from', u'on', u's', u'be', u'that', u'it', \n",
    "                   u'by', u'are', u'for', u'with', u'was', u'as', u'is', u'to', u'a', u'in', u'of', u'and', u'the']\n",
    "rejected_wordsR.extend([i[0] for i in sorted([(i, word_dict[i]) for i in word_dict], key=lambda i: i[1]) if i[1]<=2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print len(rejected_wordsR), len(word_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def saveWikiText(filename, text, rejected=[]):\n",
    "    #text2write=''\n",
    "    #for sent in text:\n",
    "    #    for word in sent:\n",
    "    #        if word in rejected: continue\n",
    "    #        text2write+=' '+word\n",
    "    #    text2write+='.'\n",
    "    #text_file = open(filename, \"w\")\n",
    "    #text_file.write(text2write)\n",
    "    #text_file.close()\n",
    "    with open(filename,'w') as f:\n",
    "        pickle.dump([[word for word in sent if word not in rejected] for sent in text],f)\n",
    "    \n",
    "    \n",
    "def loadWikiText(filename, rejected=[]):\n",
    "    #text_file = open(fname, \"r\")\n",
    "    #newtext = text_file.read()\n",
    "    #text_file.close()\n",
    "\n",
    "    #sentences = newtext.rsplit('.')\n",
    "    #text = [i.rsplit(' ') for i in newtext.rsplit('.')]\n",
    "    #text = [[j for j in i if j!=''] for i in text]\n",
    "    #text = [i for i in text if len(i)]\n",
    "    text=[]\n",
    "    with open(filename,'r') as f:\n",
    "        text = pickle.load(f)\n",
    "    text1=[]\n",
    "    for sentence in tqdm(text):\n",
    "        if len(sentence)==0: continue\n",
    "        text1.append([j for j in sentence if(j!='')&(j not in rejected)])\n",
    "    return text1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "saveWikiText('wiki_text1.txt', text)\n",
    "saveWikiText('wiki_text_raw.txt', textR)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "saveWikiText('wiki_text_rejected.txt', [rejected_words, rejected_wordsR])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rejected_words, rejected_wordsR = loadWikiText('wiki_text_rejected.txt')\n",
    "text  = loadWikiText('wiki_text1.txt', rejected_words)\n",
    "textR = loadWikiText('wiki_text_raw.txt', rejected_wordsR)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Натренируем word2vec на наших выборках. Здесь нам придется сделать некоторое допущение: многие породы собак состоят более чем из 1 слова, поэтому небходио решить: складывать векторы этих слов, или складвать и делить на N. Далее проверка будет проводиться для обоих этих вариантов"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_WV(text, breeds, freq_words=['']):\n",
    "    model = Word2Vec(text, min_count=1)\n",
    "    word_vectors = model.wv\n",
    "    del model\n",
    "    breed2vec={}\n",
    "    breed2vecN={}\n",
    "    for breed in breeds:\n",
    "        lwords = breed.replace('_', ' ').replace('-', ' ').rsplit(' ')\n",
    "        lwords = [stem(word) for word in lwords if stem(word) not in freq_words]\n",
    "        lwords = [word_vectors[word] for word in lwords if word in word_vectors.vocab]\n",
    "        if len(lwords)==0:\n",
    "            print 'Problem with breed', breed\n",
    "            continue\n",
    "        dog_vec = [sum([word[i] for word in lwords]) for i in range(len(lwords[0]))]\n",
    "        breed2vec[breed]=dog_vec\n",
    "        dog_vec = [sum([word[i] for word in lwords])/float(len(lwords)) for i in range(len(lwords[0]))]\n",
    "        breed2vecN[breed]=dog_vec\n",
    "    return breed2vec, breed2vecN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "breed2vec, breed2vecN = train_WV(text, breeds)\n",
    "breed2vecR, breed2vecRN = train_WV(textR, breeds)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print 'Number of the dog\\'s space dimensions:', len(breed2vec.values()[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Итого мы имеем 4 набора различных меток, каждый из которых имеет размерность 100. На самом деле уже сейчас это некоторый прогресс, так-как в случае многоклассового классификатора он бы, по сути, имел дело с пространством размерности 120 и метками вида (0, 0, ... 0, 1, 0, ... 0, 0). Но попробуем сделать PCA преобразование полученных векторов.\n",
    "\n",
    "Важно отметить: в пространстве векторов слов работает евклидова метрика для определения их похожести, поэтому нормировать их перед трансформацией нельзя."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def dog_voc_transformator(breed2vec):\n",
    "    voc_vals=breed2vec.values()\n",
    "    dog_pca = PCA(random_state=137, n_components=len(voc_vals[0]))\n",
    "    X_t = dog_pca.fit_transform(voc_vals)\n",
    "\n",
    "    disp=[0]\n",
    "    for nd in range(X_t.shape[1]):\n",
    "        ar = [i[nd] for i in X_t]\n",
    "        me = np.mean(ar)\n",
    "        disp.append(disp[-1]+sum([(i-me)**2 for i in ar])/X_t.shape[0])\n",
    "\n",
    "    n=0\n",
    "    for i in disp:\n",
    "        if(disp[-1]*0.99<i): break\n",
    "        n+=1\n",
    "    print 'Number of the principal components for 99% dispersion finesse is', n\n",
    "    \n",
    "    n=0\n",
    "    for i in disp:\n",
    "        if(disp[-1]*0.9<i): break\n",
    "        n+=1\n",
    "    print 'Number of the principal components for 90% dispersion finesse is', n\n",
    "\n",
    "    plt.plot(range(len(disp)), disp)\n",
    "    plt.plot([0,len(disp)], [0.9*disp[-1],0.9*disp[-1]], c='black')\n",
    "    plt.plot([n,n], [0,disp[-1]], c='red')\n",
    "    plt.show()\n",
    "    \n",
    "    return X_t, dog_pca"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_t, dog_pca = dog_voc_transformator(breed2vec)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим, что метрика сохранилась:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print np.sqrt(sum([ (i[0]-i[1])**2 for i in zip(breed2vec.values()[0],breed2vec.values()[1])]))\n",
    "print np.sqrt(sum([ (i[0]-i[1])**2 for i in zip(X_t[0],X_t[1])]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "XN_t, dog_pcaN = dog_voc_transformator(breed2vecN)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "XR_t, dog_pcaR = dog_voc_transformator(breed2vecR)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "XRN_t, dog_pcaRN = dog_voc_transformator(breed2vecRN)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что вектора собак образуют подпространство существенно меньшей размерности. При этом для узкого набора статей из википедии размерность этого пространства оказывается всего лишь около 4, тогда как для широкого набора статей это число в районе 20. При этом сложно сказать, какой из этих результатов для нас лучше: с одной стороны малое число компонент упрощает классификатор и снижает вероятность переобучения. Сдругой стороны на большом числе компонент можно, вероятно, добиться лучших реультатов"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на то, как выглядит распределение пород в проекции на первые 2 компоненты"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter([i[0] for i in X_t], [i[1] for i in X_t],[i[2] for i in X_t])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тут видна доволно интересная структура кластеров, однако давайте посмотрим повнимательнее на крайний правый из них"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in zip(X_t, breed2vec.keys()):\n",
    "    if i[0][0]>8: print i[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что его образовали собаки с большим числом слов в названии. Вероятно причина возникновения других кластеров аналогична. Такое разделение не имеет смысла, поэтому просто забудем про не отнормированные векторы.\n",
    "\n",
    "Посмотрим, как выглядят кластеры для нормированных векторов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = plt.figure(1, figsize=(20, 10))\n",
    "grid = ImageGrid(fig, 111, nrows_ncols=(1, 2), axes_pad=0.05)\n",
    "grid[0].scatter([i[0] for i in XN_t], [i[1] for i in XN_t],[i[2] for i in XN_t], color='r')\n",
    "grid[0].set_title('Narrow set')\n",
    "grid[1].scatter([i[0] for i in XRN_t],[i[1] for i in XRN_t],[i[2] for i in XRN_t], color='r')\n",
    "grid[1].set_title('Wide set of the wikipedia articles')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На первый взгляд меньшая выборка статей дает более интересную структуру кластеров. Но не стоит забывать, что у широкой выборки статей существенный процент дисперсии приходится и на другие компоненты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "breed2vecN_PCA3,breed2vecN_PCA10 = {}, {}\n",
    "for j,breed in enumerate(breed2vec):\n",
    "    breed2vecN_PCA3[breed]  = XN_t[j][:3]\n",
    "    breed2vecN_PCA10[breed] = XN_t[j][:10]\n",
    "\n",
    "breed2vecRN_PCA10,breed2vecRN_PCA20 = {}, {}\n",
    "for j,breed in enumerate(breed2vecR):\n",
    "    breed2vecRN_PCA10[breed] = XRN_t[j][:10]\n",
    "    breed2vecRN_PCA20[breed] = XRN_t[j][:20]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь, когда все данные обработаны, можно начинать делать предсказания.\n",
    "\n",
    "Сейчас у нас есть следующие датасеты:\n",
    "Векторы собак: breed2vecN_PCA3, breed2vecN_PCA10, breed2vecRN_PCA10, breed2vecRN_PCA20 \n",
    "\n",
    "Предобработанные изображения: vgg_bf and vgg_bfP\n",
    "\n",
    "Для того, чтобы делать предсказания будем  делать регрессию из фич изображения в компоненты векторов пород, а затем выбирать самую ближайшую породу. Этот подход позволит в будующем делать и вероятностные предсказания, ставя вероятность каждой из породы пропорционально расстоянию от предсказанной точки до нее.\n",
    "\n",
    "В такой ситуации очевидным выбором для метрики является квадрат ошибки, \n",
    "Так-как в пространстве слов работает евклидова метрика, которая является суммой квадаратов расстояний для каждой из компонент, то наилучшей метрикой для классификатора будет квадрат ошибки."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "voc_names = ['breed2vecN_PCA10', 'breed2vecN_PCA3', 'breed2vecRN_PCA10', 'breed2vecRN_PCA20']\n",
    "img_names = ['vgg_bfP', 'vgg_bf']\n",
    "\n",
    "for img_iter, img_fea in enumerate([vgg_bfP, vgg_bf]):\n",
    "    for voc_iter, b2v in enumerate([breed2vecN_PCA10, breed2vecN_PCA3, breed2vecRN_PCA10, breed2vecRN_PCA20]):\n",
    "        \n",
    "        print('Dataset: '+voc_names[voc_iter]+', '+img_names[img_iter])\n",
    "        \n",
    "        y = np.array([b2v[i] for i in labels.breed.values[ftrain_idx] if i in b2v])       #координаы векторов собак для регрессии\n",
    "        img_fea1 = np.array(img_fea)[np.array([(i in b2v) for i in labels.breed.values[ftrain_idx]])] #предобработанные изображения\n",
    "        target = np.array([i for i in labels.breed.values[ftrain_idx] if i in b2v])   #таргет для измерения точности на валидации\n",
    "        \n",
    "        np.random.seed(seed=SEED)\n",
    "        rnd = np.random.random(len(y))\n",
    "        train_idx=[i<0.8 for i in rnd]\n",
    "        valid_idx=[i>=0.8 for i in rnd]\n",
    "\n",
    "        y_train = y[train_idx]              \n",
    "        y_valid = y[valid_idx]\n",
    "        X_train = img_fea1[train_idx]\n",
    "        X_valid = img_fea1[valid_idx]\n",
    "        target_train = target[train_idx]\n",
    "        target = target[valid_idx]\n",
    "        \n",
    "        #breed_dict={}\n",
    "        #for i in np.unique(target): breed_dict[i]=len(breed_dict)\n",
    "        #target = np.array([breed_dict[i] for i in target])\n",
    "        #target_tr = target[train_idx]\n",
    "        #target = target[[not i for i in train_idx]]\n",
    "        \n",
    "        predictions = []\n",
    "        predictions_train = []\n",
    "        for n in range(len(y_train[0])):\n",
    "            regressor1 = LinearRegression()\n",
    "            regressor1.fit(X_train, [j[n] for j in y_train])\n",
    "            prediction = regressor1.predict(X_valid)\n",
    "            predictions.append(prediction)\n",
    "            prediction = regressor1.predict(X_train)\n",
    "            predictions_train.append(prediction)\n",
    "        predictions = [[i[n] for i in predictions] for n in range(len(predictions[0]))]\n",
    "        predictions_train = [[i[n] for i in predictions_train] for n in range(len(predictions_train[0]))]\n",
    "        \n",
    "        #valid_probs=[]\n",
    "        valid_preds=[]\n",
    "        for vect in predictions:\n",
    "            distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "            valid_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "        #    sumd=sum([i[1] for i in distances])\n",
    "        #    valid_probs.append([i[1]/sumd for i in distances])\n",
    "        #print('LogLoss {}'.format(log_loss(target, valid_probs)))\n",
    "        print('Accuracy {}'.format(accuracy_score(target, valid_preds)))\n",
    "        \n",
    "        train_preds=[]\n",
    "        for vect in predictions_train:\n",
    "            distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "            train_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "        print('Accuracy on train {}'.format(accuracy_score(target_train, train_preds)))\n",
    "        \n",
    "        #regressor2 = Ridge()\n",
    "        print '_____________________________________________________________________________'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Лучшее, чего смогла добиться голая регрессия это точность в 7.8%. Очень плохой результат, по сравнению с 67% точностью простого классификатора. Однако это намного лучше, чем случайный классификатор, для которого точность была бы порядка 1/120, то есть около 1%.\n",
    "\n",
    "При этом видно, что какого-то сильного переобучения на трейне нет. Очевидно, что линейной регрессии попросту не хватает и в этой задаче нужно, как минимум, добавить полиномиальные признаки и строить новую регрессию, или же строить леса.\n",
    "\n",
    "Кроме того стоит отметить, что PCA преобразование в этой задаче показывает себя не слишком эффективно. Обрезание 5% дисперсии отрезает более 10% от точности. Вероятно предобработанные изображения вообще не нужно преобразовывать, а от вектора собак оставить еще больш компонент - порядка 30-40.\n",
    "\n",
    "Учитывая количество признаков (512 до и 273 после РСА) количество полниномиальных признаков превысит 50000, что не очень хорошо... Построение леса в такой ситуации кажется более простой задачей\n",
    "\n",
    "Давайте, для начала, посмотрим, кого же мы сейчас путаем"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print zip(target, valid_preds)[:20]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = plt.figure(1, figsize=(16, 16))\n",
    "grid = ImageGrid(fig, 111, nrows_ncols=(2, 4), axes_pad=0.05)\n",
    "i=0\n",
    "for img_id, breed in zip(labels.id.values, labels.breed.values):\n",
    "    for i in range(4):\n",
    "        if breed==valid_preds[i]: \n",
    "            ax = grid[i+4]\n",
    "            img = read_img(img_id, 'train', (224, 224))\n",
    "            ax.imshow(img / 255.)\n",
    "        if breed==target[i]: \n",
    "            ax = grid[i]\n",
    "            img = read_img(img_id, 'train', (224, 224))\n",
    "            ax.imshow(img / 255.)\n",
    "        i+=1\n",
    "        if i>500: break\n",
    "grid[0].set_ylabel('Targets')\n",
    "grid[4].set_ylabel('Predictions')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "В целом собаки на картинках кажутся довольно похожими, что не может не радовать. Это значит, что word2vec выдает довольно осмысленные значения для предсказаний, и они как-то корелируют с тем, как на самом деле выглядят собаки."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "img_fea = vgg_bf\n",
    "b2v = breed2vecRN_PCA20\n",
    "y = np.array([b2v[i] for i in labels.breed.values[ftrain_idx] if i in b2v])       #координаы векторов собак для регрессии\n",
    "img_fea1 = np.array(img_fea)[np.array([(i in b2v) for i in labels.breed.values[ftrain_idx]])] #предобработанные изображения\n",
    "target = np.array([i for i in labels.breed.values[ftrain_idx] if i in b2v])   #таргет для измерения точности на валидации\n",
    "\n",
    "np.random.seed(seed=SEED+1)\n",
    "rnd = np.random.random(len(y))\n",
    "train_idx=[i<0.8 for i in rnd]\n",
    "valid_idx=[i>=0.8 for i in rnd]\n",
    "\n",
    "y_train = y[train_idx]              \n",
    "y_valid = y[valid_idx]\n",
    "X_train = img_fea1[train_idx]\n",
    "X_valid = img_fea1[valid_idx]\n",
    "target_train = target[train_idx]\n",
    "target = target[valid_idx]\n",
    "        \n",
    "predictions = []\n",
    "predictions_train = []\n",
    "for n in range(len(y_train[0])):\n",
    "    regressor1 = LinearRegression()\n",
    "    regressor1.fit(X_train, [j[n] for j in y_train])\n",
    "    prediction = regressor1.predict(X_valid)\n",
    "    predictions.append(prediction)\n",
    "    prediction = regressor1.predict(X_train)\n",
    "    predictions_train.append(prediction)\n",
    "predictions = [[i[n] for i in predictions] for n in range(len(predictions[0]))]\n",
    "predictions_train = [[i[n] for i in predictions_train] for n in range(len(predictions_train[0]))]\n",
    "        \n",
    "valid_preds=[]\n",
    "ldistances=[]\n",
    "for vect, trg in zip(predictions,target):\n",
    "    distances = sorted([(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v], key=lambda i: i[1])\n",
    "    valid_preds.append(distances[0][0])\n",
    "    distance_to_trg = np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[trg],vect)]))\n",
    "    if(distances[0][1]!=0): ldistances.append([distances[0][1], distance_to_trg/distances[0][1], distances[1][1]/distances[0][1]])\n",
    "print('Accuracy {}'.format(accuracy_score(target, valid_preds)))\n",
    "\n",
    "train_preds=[]\n",
    "for vect in predictions_train:\n",
    "    distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "    train_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "print('Accuracy on train {}'.format(accuracy_score(target_train, train_preds)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При этом точность довольно сильно скачет в зависимости от разбиения на трейн и тест. Два разных сида выдают результат 6.8% и 7.5% соответственно. Данных довольно мало для того, чтобы эта задача выдавала стабильный результат."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(1)\n",
    "plt.subplot(121)\n",
    "plt.hist([i[1] for i in ldistances], bins=80, range=[1,4])\n",
    "plt.title('D(target)/D(closest)')\n",
    "\n",
    "plt.subplot(122)\n",
    "plt.hist([i[2] for i in ldistances], bins=80, range=[1,2])\n",
    "plt.title('D(next to closest)/D(closest)')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Представить себе распределение дистанций между объектами в 20-мерном пространстве довольно сложно, поэтому сравним дистанцию до объекта, следующего после ближайшего, и до таргета, отнормировав их на дитанцию до ближайшего объекта. При этом видно, что ошибается регрессор зачастую очень сильно, так-как дисперсия этих распределений отличается во много раз. Однако, при взгляде на левую картинку становится видно, что она скорее похожа на сумму двух распределений: узкого и широкого. То есть регрессор, скорее всего, довольно успешно обучился на какие-то породы собак(какой-то кластер в пространстве word vectors), но очень плохо предсказывает другие. Проверим это предположение."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N1=50 #количество показанных пород\n",
    "N2=70\n",
    "\n",
    "matrice = [[0 for i in range(121)] for j in range(121)]\n",
    "for i in zip(target,valid_preds):\n",
    "    matrice[breed_dict[i[0]]][breed_dict[i[1]]]+=1\n",
    "\n",
    "matrice_labels = ['' for i in range(121)]\n",
    "for j in breed_dict.keys():\n",
    "    matrice_labels[breed_dict[j]]=j\n",
    "    tot=sum([i==j for i in target])\n",
    "    for i in breed_dict.keys():\n",
    "        matrice[breed_dict[j]][breed_dict[i]]/=float(tot)\n",
    "\n",
    "matrice_train = [[0 for i in range(121)] for j in range(121)]\n",
    "for i in zip(target_train,train_preds):\n",
    "    matrice_train[breed_dict[i[0]]][breed_dict[i[1]]]+=1\n",
    "\n",
    "for j in breed_dict.keys():\n",
    "    tot=sum([i==j for i in target_train])\n",
    "    for i in breed_dict.keys():\n",
    "        matrice_train[breed_dict[j]][breed_dict[i]]/=float(tot)\n",
    "        \n",
    "plt.figure(2)\n",
    "plt.subplot(221)\n",
    "sns.heatmap([i[N1:N2] for i in matrice[N1:N2]], yticklabels=matrice_labels[N1:N2], xticklabels='')\n",
    "plt.subplot(222)\n",
    "sns.heatmap([i[80:100] for i in matrice[N1:N2]], xticklabels='', yticklabels='')\n",
    "plt.title(u'Строки матрицы соответствуют таргетам, столбцы - предсказаниям')\n",
    "\n",
    "plt.subplot(223)\n",
    "sns.heatmap([i[N1:N2] for i in matrice_train[N1:N2]], xticklabels=matrice_labels[N1:N2], yticklabels=matrice_labels[N1:N2])\n",
    "plt.subplot(224)\n",
    "sns.heatmap([i[80:100] for i in matrice_train[N1:N2]], xticklabels=matrice_labels[80:100],yticklabels='')\n",
    "plt.title(u'Ситуация на трейне')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вот хорошй пример. Классификатор относительно хорошо научился предсказывать некоторые классы, например irish water spaniel и japanese spaniel.\n",
    "В то время, как irish wolfhound ошибочно называется scottish deerhound, а irish setter путается с irish wolfhound. \n",
    "\n",
    "Но, что самое интересное, ситуация на трейне и тесте абсолютно идентична. Может быть эту проблему можно решить простой перестановкой строк?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bayesian = {}\n",
    "for i in zip(train_preds, target_train):\n",
    "    if i[0] not in bayesian:\n",
    "        bayesian[i[0]] = np.array([0]*121)\n",
    "    bayesian[i[0]][breed_dict[i[1]]]+=1\n",
    "\n",
    "changer ={}\n",
    "for i in bayesian:\n",
    "    maximal=0\n",
    "    bestbreed =''\n",
    "    arr = bayesian[i]\n",
    "    for breed in breed_dict:\n",
    "        if(bayesian[i][breed_dict[breed]]<=maximal): continue\n",
    "        maximal=bayesian[i][breed_dict[breed]]\n",
    "        bestbreed=breed\n",
    "    changer[i]=bestbreed\n",
    "\n",
    "for breed in breed_dict:\n",
    "    if breed not in changer:\n",
    "        changer[breed]=breed\n",
    "\n",
    "print('Accuracy on train {}'.format(accuracy_score(target_train, [changer[i] for i in train_preds])))\n",
    "print('Accuracy on valid {}'.format(accuracy_score(target, [changer[i] for i in valid_preds])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Точность немного выростает от такого преобразования..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = plt.figure(1, figsize=(16, 16))\n",
    "grid = ImageGrid(fig, 111, nrows_ncols=(2, 4), axes_pad=0.05)\n",
    "i=0\n",
    "j=0\n",
    "for img_id, breed in zip(labels.id.values, labels.breed.values):\n",
    "        if breed=='irish_setter': \n",
    "            if i==4: continue\n",
    "            ax = grid[i]\n",
    "            img = read_img(img_id, 'train', (224, 224))\n",
    "            ax.imshow(img / 255.)\n",
    "            i+=1\n",
    "        if breed=='irish_wolfhound': \n",
    "            if j==4: continue\n",
    "            ax = grid[j+4]\n",
    "            img = read_img(img_id, 'train', (224, 224))\n",
    "            ax.imshow(img / 255.)\n",
    "            j+=1\n",
    "        if i+j==8: break\n",
    "grid[0].set_ylabel('irish_setter')\n",
    "grid[4].set_ylabel('irish_wolfhound')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Не самые похожие породы, но, кажется, классификатор нашел какое-то качество в изображении, которое предсказывает, что собака именно ирландская. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calc_word_accuracy(word):\n",
    "    acc=0.\n",
    "    tot=0.\n",
    "    for breed in zip(target, valid_preds):\n",
    "        if word in breed[0].split('_'): \n",
    "            if word in breed[1].split('_'): acc+=1\n",
    "        if word not in breed[0].split('_'): \n",
    "            if word not in breed[1].split('_'): acc+=1\n",
    "        tot+=1.\n",
    "    print 'Accuracy of', word, '=', acc/tot\n",
    "calc_word_accuracy('irish')\n",
    "calc_word_accuracy('japanese')\n",
    "calc_word_accuracy('scottish')\n",
    "calc_word_accuracy('wolfhound')\n",
    "calc_word_accuracy('setter')\n",
    "calc_word_accuracy('spaniel')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calc_word_accuracy_logistic2(word):\n",
    "    y = np.array([breed_dict[i] for i in labels.breed.values])\n",
    "    logreg = LogisticRegression(multi_class='multinomial', solver = 'lbfgs', random_state=137, C=0.05) \n",
    "    # C выбрано исходя из валидационной кривой в начале\n",
    "    logreg.fit(np.array(vgg_bfP)[train_idx], y[train_idx])\n",
    "    log_pred = logreg.predict(np.array(vgg_bfP)[valid_idx])\n",
    "    log_targ = y[valid_idx]\n",
    "    target2word={}\n",
    "    for breed in breed_dict: target2word[breed_dict[breed]]=int(word in breed.split('_'))\n",
    "    log_targ = [target2word[i] for i in log_targ]\n",
    "    log_pred = [target2word[i] for i in log_pred]\n",
    "    print 'Accuracy for word', word, '=', accuracy_score(log_pred, log_targ)\n",
    "    \n",
    "calc_word_accuracy_logistic2('irish')\n",
    "calc_word_accuracy_logistic2('japanese')\n",
    "calc_word_accuracy_logistic2('scottish')\n",
    "calc_word_accuracy_logistic2('wolfhound')\n",
    "calc_word_accuracy_logistic2('setter')\n",
    "calc_word_accuracy_logistic2('spaniel')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что в целом алгоритм с использованием word2vec неплохо способен выделять свойства изображения, хотя логистическая регрессия его все еще опережает."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим теперь случайный лес."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "img_fea = vgg_bf\n",
    "b2v = breed2vecRN_PCA20\n",
    "y = np.array([b2v[i] for i in labels.breed.values[ftrain_idx] if i in b2v])       #координаы векторов собак для регрессии\n",
    "img_fea1 = np.array(img_fea)[np.array([(i in b2v) for i in labels.breed.values[ftrain_idx]])] #предобработанные изображения\n",
    "target = np.array([i for i in labels.breed.values[ftrain_idx] if i in b2v])   #таргет для измерения точности на валидации\n",
    "\n",
    "np.random.seed(seed=SEED)\n",
    "rnd = np.random.random(len(y))\n",
    "train_idx=[i<0.8 for i in rnd]\n",
    "valid_idx=[i>=0.8 for i in rnd]\n",
    "\n",
    "y_train = y[train_idx]              \n",
    "y_valid = y[valid_idx]\n",
    "X_train = img_fea1[train_idx]\n",
    "X_valid = img_fea1[valid_idx]\n",
    "target_train = target[train_idx]\n",
    "target = target[valid_idx]\n",
    "        \n",
    "predictions = []\n",
    "predictions_train = []\n",
    "for n in tqdm(range(len(y_train[0]))):\n",
    "    regressor1 = RandomForestRegressor(max_depth=20, max_features=50, n_estimators=100)\n",
    "    regressor1.fit(X_train, [j[n] for j in y_train])\n",
    "    prediction = regressor1.predict(X_valid)\n",
    "    predictions.append(prediction)\n",
    "    prediction = regressor1.predict(X_train)\n",
    "    predictions_train.append(prediction)\n",
    "predictions = [[i[n] for i in predictions] for n in range(len(predictions[0]))]\n",
    "predictions_train = [[i[n] for i in predictions_train] for n in range(len(predictions_train[0]))]\n",
    "        \n",
    "valid_preds=[]\n",
    "ldistances=[]\n",
    "for vect, trg in zip(predictions,target):\n",
    "    distances = sorted([(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v], key=lambda i: i[1])\n",
    "    valid_preds.append(distances[0][0])\n",
    "    distance_to_trg = np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[trg],vect)]))\n",
    "    if(distances[0][1]!=0): ldistances.append([distances[0][1], distance_to_trg/distances[0][1], distances[1][1]/distances[0][1]])\n",
    "print('Accuracy {}'.format(accuracy_score(target, valid_preds)))\n",
    "\n",
    "train_preds=[]\n",
    "for vect in predictions_train:\n",
    "    distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "    train_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "print('Accuracy on train {}'.format(accuracy_score(target_train, train_preds)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Даже после перебора параметров по довольно большому гриду рэндом форест показывает результат хуже, чем простая регрессия. Интересно..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Попробуем, все таки, улучшить регрессию. Просто строить полиномильные признаки в данном случае не получится - на базе 512 исходных признаков число полиномиальных признаков будет около 150000, что с вычислительной точки зрения мне совершенно не выгодно. Поэтому будем строить полиномиальные признаки на части компонент РСА преобразования, а оставшиеся оставим в исходном виде. Первые 100 компонет объясняют около 65% дисперсии, и число признаков сразу падает до 5562. Так-как это гиперпараметр будущей модели, то сделаем перебор:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vgg_bfPfull = pca.transform(vgg_scaler.transform(vgg_bf))\n",
    "pol10 =[i[:10]  for i in vgg_bfPfull]\n",
    "pol30 =[i[:30]  for i in vgg_bfPfull]\n",
    "pol50 =[i[:50]  for i in vgg_bfPfull]\n",
    "pol70 =[i[:70]  for i in vgg_bfPfull]\n",
    "pol100=[i[:100] for i in vgg_bfPfull]\n",
    "\n",
    "\n",
    "def polynomizator(line):\n",
    "    l = len(line)\n",
    "    newline=[]\n",
    "    for a in range(l):\n",
    "        for b in range(a,l):\n",
    "            newline.append(line[a]*line[b])\n",
    "    return newline\n",
    "\n",
    "pol10  = [polynomizator(i) for i in pol10]\n",
    "pol30  = [polynomizator(i) for i in pol30]\n",
    "pol50  = [polynomizator(i) for i in pol50]\n",
    "pol70  = [polynomizator(i) for i in pol70]\n",
    "pol100 = [polynomizator(i) for i in pol100]\n",
    "\n",
    "vgg_bfPfull10  = np.hstack([vgg_bfPfull, pol10])\n",
    "vgg_bfPfull30  = np.hstack([vgg_bfPfull, pol30])\n",
    "vgg_bfPfull50  = np.hstack([vgg_bfPfull, pol50])\n",
    "vgg_bfPfull70  = np.hstack([vgg_bfPfull, pol70])\n",
    "vgg_bfPfull100 = np.hstack([vgg_bfPfull, pol100])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b2v = breed2vecRN_PCA20\n",
    "y = np.array([b2v[i] for i in labels.breed.values[ftrain_idx] if i in b2v])       #координаы векторов собак для регрессии\n",
    "target = np.array([i for i in labels.breed.values[ftrain_idx] if i in b2v])   #таргет для измерения точности на валидации\n",
    "        \n",
    "y_train = y[train_idx]              \n",
    "y_valid = y[valid_idx]\n",
    "target_train = target[train_idx]\n",
    "target = target[valid_idx]\n",
    "        \n",
    "li = [10, 30, 50, 70, 100]\n",
    "ind=0\n",
    "for img_fea in [vgg_bfPfull10, vgg_bfPfull30, vgg_bfPfull50, vgg_bfPfull70]: #, vgg_bfPfull100]:\n",
    "    print li[ind], 'components polynomial features'\n",
    "    ind+=1\n",
    "    img_fea1 = np.array(img_fea)[np.array([(i in b2v) for i in labels.breed.values[ftrain_idx]])] #предобработанные изображения\n",
    "    X_train = img_fea1[train_idx]\n",
    "    X_valid = img_fea1[valid_idx]\n",
    "\n",
    "    predictions = []\n",
    "    predictions_train = []\n",
    "    for n in range(len(y_train[0])):\n",
    "        regressor1 = LinearRegression()\n",
    "        regressor1.fit(X_train, [j[n] for j in y_train])\n",
    "        prediction = regressor1.predict(X_valid)\n",
    "        predictions.append(prediction)\n",
    "        prediction = regressor1.predict(X_train)\n",
    "        predictions_train.append(prediction)\n",
    "    predictions = [[i[n] for i in predictions] for n in range(len(predictions[0]))]\n",
    "    predictions_train = [[i[n] for i in predictions_train] for n in range(len(predictions_train[0]))]\n",
    "\n",
    "    valid_preds=[]\n",
    "    ldistances=[]\n",
    "    for vect, trg in zip(predictions,target):\n",
    "        distances = sorted([(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v], key=lambda i: i[1])\n",
    "        valid_preds.append(distances[0][0])\n",
    "        distance_to_trg = np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[trg],vect)]))\n",
    "        if(distances[0][1]!=0): ldistances.append([distances[0][1], distance_to_trg/distances[0][1], distances[1][1]/distances[0][1]])\n",
    "    print('Accuracy {}'.format(accuracy_score(target, valid_preds)))\n",
    "\n",
    "    train_preds=[]\n",
    "    for vect in predictions_train:\n",
    "        distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "        train_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "    print('Accuracy on train {}'.format(accuracy_score(target_train, train_preds)))\n",
    "    print '-------------------------------------------------------------------'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Точность растет довольно значимо на первых 50, и это очень радует, а на 70 уже начинает падать из-за переобучения на тысячи лишних признаков. В такой задаче, разумеется, надо применять какой-то регрессор с регуляризацией. Попробуем применить ridge регрессор.  \n",
    "\n",
    "Выше результат построения полиномиальных признаков на 100 оригинальных не показан, так-как тренировка на них занимает какое-то слишком уж большое время и я просто не смог дождаться ее завершения. Однако ridge регрессор, кажется, оптимизирован под задачи с большим количеством признаков и справляется на порядок быстрее, так что данные о 100 полиномиальных признаках снова доступны."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_ridge(img_fea, alphas = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, \n",
    "                                   300, 500, 1000, 2000, 3000, 5000, 10000, 20000, 50000]):\n",
    "    img_fea1 = np.array(img_fea)[np.array([(i in b2v) for i in labels.breed.values[ftrain_idx]])]\n",
    "    X_train = img_fea1[train_idx]\n",
    "    X_valid = img_fea1[valid_idx]\n",
    "    accuracies = []\n",
    "    accuracies_tr = []\n",
    "    for alpha in tqdm(alphas):\n",
    "        predictions = []\n",
    "        predictions_train = []\n",
    "        for n in range(len(y_train[0])):\n",
    "            regressor1 = Ridge(alpha=alpha)\n",
    "            regressor1.fit(X_train, [j[n] for j in y_train])\n",
    "            prediction = regressor1.predict(X_valid)\n",
    "            predictions.append(prediction)\n",
    "            prediction = regressor1.predict(X_train)\n",
    "            predictions_train.append(prediction)\n",
    "        predictions = [[i[n] for i in predictions] for n in range(len(predictions[0]))]\n",
    "        predictions_train = [[i[n] for i in predictions_train] for n in range(len(predictions_train[0]))]\n",
    "\n",
    "        valid_preds=[]\n",
    "        ldistances=[]\n",
    "        for vect, trg in zip(predictions,target):\n",
    "            distances = sorted([(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v], key=lambda i: i[1])\n",
    "            valid_preds.append(distances[0][0])\n",
    "        accuracies.append(accuracy_score(target, valid_preds))\n",
    "\n",
    "        train_preds=[]\n",
    "        for vect in predictions_train:\n",
    "            distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "            train_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "        accuracies_tr.append(accuracy_score(target_train, train_preds))\n",
    "    print 'Best accuracy:', max(accuracies)\n",
    "    print 'Best alpha:', sum([i[1] for i in zip(accuracies, alphas) if i[0]==max(accuracies)])\n",
    "    return alphas, accuracies, accuracies_tr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "al, ac, ac_tr = train_ridge(vgg_bfPfull50)\n",
    "plt.plot([np.log(i) for i in al], ac_tr, color='r')\n",
    "plt.plot([np.log(i) for i in al], ac, color='b')\n",
    "plt.title('polynomial components based on 50 principal features')\n",
    "plt.xlabel('Log(alpha)')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "al, ac, ac_tr = train_ridge(vgg_bfPfull70, alphas = [5, 10, 20, 50, 100, 200, 300, 500, 1000, 2000, 3000, 5000, 10000, 20000, 50000])\n",
    "plt.plot([np.log(i) for i in al], ac_tr, color='r')\n",
    "plt.plot([np.log(i) for i in al], ac, color='b')\n",
    "plt.title('polynomial features based on 70 principal components')\n",
    "plt.xlabel('Log(alpha)')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "al, ac, ac_tr = train_ridge(vgg_bfPfull100, alphas = [100, 200, 500, 1000, 2000, 3000, 5000])\n",
    "plt.plot([np.log(i) for i in al], ac_tr, color='r')\n",
    "plt.plot([np.log(i) for i in al], ac, color='b')\n",
    "plt.title('polynomial features based on 100 principal components')\n",
    "plt.xlabel('Log(alpha)')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "al, ac, ac_tr = train_ridge(vgg_bfPfull100, alphas = [5000, 10000, 20000, 30000, 50000, 100000])\n",
    "plt.plot([np.log(i) for i in al], ac_tr, color='r')\n",
    "plt.plot([np.log(i) for i in al], ac, color='b')\n",
    "plt.title('polynomial features based on 100 principal components')\n",
    "plt.xlabel('Log(alpha)')\n",
    "plt.ylabel('Accuracy')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Наилучшая модель выбрана. Проверим ее на холдауте"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vgg_bfPhold = pca.transform(vgg_scaler.transform(vgg_bf_holdout))\n",
    "pol100 =[i[:100]  for i in vgg_bfPhold]\n",
    "pol100 = [polynomizator(i) for i in pol100]\n",
    "vgg_bfPhold100 = np.hstack([vgg_bfPhold, pol100])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "img_fea = vgg_bfPfull100\n",
    "img_fea1 = np.array(img_fea)[np.array([(i in b2v) for i in labels.breed.values[ftrain_idx]])]\n",
    "X_train = img_fea1[train_idx]\n",
    "X_valid = img_fea1[valid_idx]\n",
    "\n",
    "X_hold = np.array(vgg_bfPhold100)[np.array([(i in b2v) for i in labels.breed.values[holdout_idx]])]\n",
    "target_hold = [i for i in labels.breed.values[holdout_idx] if i in b2v]\n",
    "\n",
    "predictions = []\n",
    "predictions_train = []\n",
    "predictions_hold  = []\n",
    "for n in tqdm(range(len(y_train[0]))):\n",
    "    regressor1 = Ridge(alpha=10000)\n",
    "    regressor1.fit(X_train, [j[n] for j in y_train])\n",
    "    prediction = regressor1.predict(X_valid)\n",
    "    predictions.append(prediction)\n",
    "    prediction = regressor1.predict(X_train)\n",
    "    predictions_train.append(prediction)\n",
    "    prediction = regressor1.predict(X_hold)\n",
    "    predictions_hold.append(prediction)\n",
    "predictions = [[i[n] for i in predictions] for n in range(len(predictions[0]))]\n",
    "predictions_train = [[i[n] for i in predictions_train] for n in range(len(predictions_train[0]))]\n",
    "predictions_hold  = [[i[n] for i in predictions_hold] for n in range(len(predictions_hold[0]))]\n",
    "        \n",
    "valid_preds=[]\n",
    "ldistances=[]\n",
    "for vect, trg in zip(predictions,target):\n",
    "    distances = sorted([(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v], key=lambda i: i[1])\n",
    "    valid_preds.append(distances[0][0])\n",
    "    distance_to_trg = np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[trg],vect)]))\n",
    "    if(distances[0][1]!=0): ldistances.append([distances[0][1], distance_to_trg/distances[0][1], distances[1][1]/distances[0][1]])\n",
    "print('Accuracy {}'.format(accuracy_score(target, valid_preds)))\n",
    "\n",
    "train_preds=[]\n",
    "for vect in predictions_train:\n",
    "    distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "    train_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "print('Accuracy on train {}'.format(accuracy_score(target_train, train_preds)))\n",
    "\n",
    "hold_preds=[]\n",
    "for vect in predictions_hold:\n",
    "    distances = [(i, np.sqrt(sum([(j[0]-j[1])**2 for j in zip(b2v[i],vect)]))) for i in b2v]\n",
    "    hold_preds.append(sorted(distances, key=lambda i: i[1])[0][0])\n",
    "print('Accuracy on holdout {}'.format(accuracy_score(target_hold, hold_preds)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bayesian = {}\n",
    "for i in zip(train_preds, target_train):\n",
    "    if i[0] not in bayesian:\n",
    "        bayesian[i[0]] = np.array([0]*121)\n",
    "    bayesian[i[0]][breed_dict[i[1]]]+=1\n",
    "\n",
    "changer ={}\n",
    "for i in bayesian:\n",
    "    maximal=0\n",
    "    bestbreed =''\n",
    "    arr = bayesian[i]\n",
    "    for breed in breed_dict:\n",
    "        if(bayesian[i][breed_dict[breed]]<=maximal): continue\n",
    "        maximal=bayesian[i][breed_dict[breed]]\n",
    "        bestbreed=breed\n",
    "    changer[i]=bestbreed\n",
    "\n",
    "for breed in breed_dict:\n",
    "    if breed not in changer:\n",
    "        changer[breed]=breed\n",
    "\n",
    "print('Accuracy on train {}'.format(accuracy_score(target_train, [changer[i] for i in train_preds])))\n",
    "print('Accuracy on valid {}'.format(accuracy_score(target, [changer[i] for i in valid_preds])))\n",
    "print('Accuracy on holdout {}'.format(accuracy_score(target_hold, [changer[i] for i in hold_preds])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ну что же, неплохой результат, учитывая, что мы начинали с точности в 6%. \n",
    "\n",
    "Последнее преобразование явно не дало никакого эффекта, так что регрессия явно стала лучше предсказывать с добавлением полиномиальных фич. Понятное дело можно развивать инжиниринг новых фич, но мощности моего компьютера на пределе, поэтому пора остановиться."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(1)\n",
    "plt.subplot(121)\n",
    "plt.hist([i[1] for i in ldistances], bins=80, range=[1,4])\n",
    "plt.title('D(target)/D(closest)')\n",
    "\n",
    "plt.subplot(122)\n",
    "plt.hist([i[2] for i in ldistances], bins=80, range=[1,2])\n",
    "plt.title('D(next to closest)/D(closest)')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что предсказания стали зачастую идти ближе к цели, но расхождения все еще громадны. Дисперсия правого хвоста распределения изменилась довольно слабо, так что модель все еще просто не настроилась на значительную часть данных."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Заключение\n",
    "\n",
    "В целом я скорее оцениваю этот проект, как провальный в том виде, в котором он реализован сейчас. Ожидалось, что данные word2vec позволят кластеризовать метки классов так, что точность классификатора значимо улучшится, а этой цели добиться явно не удалось - точность упала в 4 раза. Очевидно, что дальше надо строить полиномиальные признаки 3 и 4 порядков, или же еще как-то модифицировать исходные данные но у меня совершенно точно не хватит ресурсов компьютера на такую задачу. Даже сейчас модель оказывается намного более тяжеловесной, чем я ожидал изначально.\n",
    "\n",
    "Однако и сам проект пока и не закончен. В планах было использование нескольких готовых word2vec, тренированных на больших данных. Как мы видели, более широкий спектр статей википедии справился намного лучше узкого. Вероятно тренированный на больших данных word2vec также улучшил бы классификацию. Но готовые word2vec плохо помещаются в память моего компьютера. \n",
    "\n",
    "В планах было использовать картинки и тэги из iNaturalist challenge, но сотня гигабайт изображений опять же убила бы мой ноутбук еще на этапе предобработки (3 Гб вошедших сюда изображений обрабатывались около 2.5 часов). Также можно было попробовать использовать другие готовые сети, например XCeption, но это опять же ресурсоемкие задачи.\n",
    "\n",
    "Я предполагаю, что с ростом статистики осмысленность векторов слов должна вырасти. Кроме того то позволит обучить более сложные модели. Сейчас вместо верхнего слоя нейронной сети, делающего предсказание в векторы слов стоит простая регрессия, или дерево, а это довольно глупо. Логично было бы достроить несколько своих полносвязных слоев на вершине нейронной сети, чтобы предсказывать векторы слов.\n",
    "\n",
    "В общем...\n",
    "\n",
    "# To be continued..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Впишите ваш код здесь"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
